#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);          // 错误处理函数
void read_childproc(int sig);                // 子进程技术的信号函数

int main(int argc, char *argv[])
{
    if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	int    serv_sock,  clnt_sock;            // 服务端和客户端的套接字
	struct sockaddr_in serv_adr, clnt_adr;   // 服务端和客户端套接字地址
	
	pid_t     pid;
	struct    sigaction act;
	socklen_t adr_sz;
	int       str_len, state;
	char      buf[BUF_SIZE];

	act.sa_handler = read_childproc;
	act.sa_flags   = 0;
    sigemptyset(&act.sa_mask);
	state     = sigaction(SIGCHLD, &act, 0);  // 注册子进城结束的信号函数
	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family      = AF_INET;
	serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_adr.sin_port        = htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
	
	while(1)
	{
		adr_sz    = sizeof(clnt_adr);
		clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);

		if(clnt_sock==-1)
			continue;
		else
			puts("new client connected...");
		
        pid = fork();             
		if(pid==-1)
		{
			close(clnt_sock);
			continue;               // fork进程失败, 直接关闭客户端套接字
		}
		if(pid==0)                  // 子进程, 客户端套接字会复制进子进程
		{
            //! 本例中，监听客户端请求只由主进程完成
            //! 子进程仅复制了套接字的文件描述符, 与父进程共享一个套接字
			close(serv_sock);       
			while((str_len=read(clnt_sock, buf, BUF_SIZE))!=0)
				write(clnt_sock, buf, str_len);
			
            //! 客户端发送完成后关闭输出流，这里会收到EOF，因此主动关闭发起关闭套接字
			close(clnt_sock);
			puts("client disconnected...");
			return 0;
		}
		else 
			close(clnt_sock);       // 直接断开客户端套接字
	}
	close(serv_sock);
	return 0;
}

void read_childproc(int sig)
{
	pid_t pid;
	int   status;
    // 子进程结束时系统发起调用的，因此返回的pid一定不为0
	pid = waitpid(-1, &status, WNOHANG);        
	printf("removed proc id: %d \n", pid);
}
void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}